/**	\author    coidgen 0.9.2
	\version    (0.0 build 7)
	\date      
	\brief     
*/



#include "mudcore_client.h"
#include "coid/comm/binstream/container_linear.h"




namespace coid {

struct COID_TABLE_MudCore_0_0 {
	opcd (COID_TABLE_MudCore_0_0::*disconnect)();
	opcd (COID_TABLE_MudCore_0_0::*register_client__0)( uint obj );
	opcd (COID_TABLE_MudCore_0_0::*unregister_client__1)( uint obj );
	opcd (COID_TABLE_MudCore_0_0::*get_creature__2)(const charstr & str, void ** cr);
	opcd (COID_TABLE_MudCore_0_0::*send_intro_screen__3)(dynarray<charstr> * arr);
};

}	// namespace coid



#define ___COID_CONNECTWITHIN_TRY		try {
#define ___COID_CONNECTWITHIN_CATCH	} catch( opcd ___coid_err ) {return ___coid_err;}\
										catch(...) { return ersFE_EXCEPTION "client method exception"; }
#define ___COID_CONNECT_TRY		try {
#define ___COID_CONNECT_CATCH		} catch( opcd ___coid_err ) {delete _coid_bstream; return ___coid_err;}\
									catch( ThreadException & ) { delete _coid_bstream; throw; }\
									catch(...) { delete _coid_bstream; return ersFE_EXCEPTION "client method exception"; }





const version COID_version_MudCore_0_0( "", 0, 0, 7 );

static COID_NICK g_coid_nick;

namespace coid {

static COID_TABLE_MudCore_0_0 * coid_interprocess_vtbl = NULL;
static COID_TABLE_MudCore_0_0 * coid_remote_vtbl = NULL;
static comm_mutex coid_table_mx( true, "COID_TABLE_MudCore_0_0" );

struct TABLE_REMOVER_MudCore_0_0 {
	~TABLE_REMOVER_MudCore_0_0() {delete coid_interprocess_vtbl; delete coid_remote_vtbl;}
};
static TABLE_REMOVER_MudCore_0_0	MudCore_0_0_table_trash;





enum {	/// command ids, format is: COID_classname_fncname_fncid
	COID_MudCore_register_client_0 = 0,
	COID_MudCore_unregister_client_1 = 1,
	COID_MudCore_get_creature_2 = 2,
	COID_MudCore_send_intro_screen_3 = 3
};




static uint COID_method_flags_MudCore[4] = {
	0x10000,
	0x10000,
	0x10000,
	0x10000
};




/***************************************************************************
MudCore_localclient
***************************************************************************/

struct MudCore_localclient
{
public:
	COID_CLIENT cc;

	static void coid_init_table( uint coid_stream_flags, COID_TABLE_MudCore_0_0 *& table );
public:
   MudCore_localclient( netstream * b, binstream * b2 ) : cc(b, b2) {}
   void coid_throw();
   opcd disconnect() { return cc.disconnect(); }


// 	opcd register_client( uint obj );
// 	opcd unregister_client( uint obj );
// 	opcd get_creature(const charstr & str, void ** cr);
// 	opcd send_intro_screen(dynarray<charstr> * arr);
};



/// fake function:
void MudCore_localclient::coid_throw() {throw ersFE_EXCEPTION;}


typedef void (MudCore_localclient::*coid_fnc)();

#define COID_CLIENT_CREATE_HOOK(M,i,t,tc)\
	if( COID_method_flags_MudCore[i] & coid_stream_flags ) ((coid_fnc *) table)[t] = (coid_fnc) (tc) &MudCore_localclient::M;\
	else ((coid_fnc *) table)[t] = (coid_fnc) &MudCore_localclient::coid_throw;

#define COID_CLIENT_CREATE_DISCONNECT_HOOK(t)\
	((coid_fnc *) table)[t] = (coid_fnc) &MudCore_localclient::disconnect;





void inline MudCore_localclient::coid_init_table( uint coid_stream_flags, COID_TABLE_MudCore_0_0 *& table )
{
	table = new COID_TABLE_MudCore_0_0;

	uint coid_table_index = 0;
	COID_CLIENT_CREATE_DISCONNECT_HOOK( coid_table_index++ )
	((coid_fnc *) table)[coid_table_index++] = (coid_fnc) &MudCore_localclient::coid_throw;  // 'register_client' is direct only
	((coid_fnc *) table)[coid_table_index++] = (coid_fnc) &MudCore_localclient::coid_throw;  // 'unregister_client' is direct only
	((coid_fnc *) table)[coid_table_index++] = (coid_fnc) &MudCore_localclient::coid_throw;  // 'get_creature' is direct only
	((coid_fnc *) table)[coid_table_index++] = (coid_fnc) &MudCore_localclient::coid_throw;  // 'send_intro_screen' is direct only
}




// method 'register_client' is direct only
// method 'unregister_client' is direct only
// method 'get_creature' is direct only
// method 'send_intro_screen' is direct only



/***************************************************************************
MudCore_client
***************************************************************************/

MudCore_client::MudCore_client() : _me(NULL), _vtbl(NULL), _conn_data(0), _timeout(0)
{
	netSubsystem::instance();
}
MudCore_client::~MudCore_client()
{
	disconnect();
}
MudCore_client::MudCore_client( const MudCore_client & c ) {throw ersFE_EXCEPTION;}
MudCore_client & MudCore_client::operator = ( const MudCore_client & c ) {throw ersFE_EXCEPTION; return *this;}

void MudCore_client::destroy_me()
{
	if( get_connection_type() != ConnectFlags::fACCESS_MODE_DIRECT )
		delete (MudCore_localclient *) _me;
	_me = NULL;
}

opcd MudCore_client::disconnect()
{
	if( !is_connected() )
		return 0;
	DASSERT( _mx_reg.is_valid() );
	comm_mutex & lowlevelmx = _mx_reg._mxc->_mx;
	bool last = false;
	opcd coid_err;
	try {
		extendedGUARD_lock( _mx_reg );
		DASSERT( _me );
		coid_err = ((get_connection_type() == ConnectFlags::fACCESS_MODE_DIRECT ? (COID_TABLE_MudCore_0_0 *) this : _me)->*(_vtbl->disconnect)) ();
		destroy_me();
		if( _mx_reg.counter_fast() == 1 ) {
			_mx_reg.disable_fast();
			lowlevelmx.unlock();
			__coid__mxg.eject();
			last = true;
		}
	}
	catch( opcd e ) {
		destroy_me(); coid_err = e;
	}

	if( last ) {
#ifdef _DEBUG
		const int max = 0xFFFF;
#else
		const int max = 10;
#endif
		int i=0;
		for( ; i<max; i++ ) {
			if( lowlevelmx.try_lock() ) {lowlevelmx.unlock(); break;}
			sysMilliSecondSleep( 10 );
		}
		if( i == max ) throw ersFE_EXCEPTION;
	}

	_mx_reg.destroy_all();
	_vtbl = NULL;
	set_connection_type( 0 );
	return coid_err;
}

opcd MudCore_client::get_last_error() const
{
	if( !is_connected() ) return 0;
	if( get_connection_type() == ConnectFlags::fACCESS_MODE_REMOTE ) return ((COID_CLIENT *) _me)->get_last_error();
	else if( get_connection_type() == ConnectFlags::fACCESS_MODE_INTERPROCESS ) return ((COID_CLIENT *) _me)->get_last_error();
	return 0;
}

void MudCore_client::set_last_error( opcd e )
{
	if( !is_connected() ) return;
	if( get_connection_type() == ConnectFlags::fACCESS_MODE_REMOTE ) ((COID_CLIENT *) _me)->set_last_error( e );
	else if( get_connection_type() == ConnectFlags::fACCESS_MODE_INTERPROCESS ) ((COID_CLIENT *) _me)->set_last_error( e );
}

inline bool MudCore_client::setup_members( uint coid_stream_flags, netstream * bstream, binstream * bstream2, comm_mutex_reg * mx_reg )
{
	disconnect();
	set_connection_type( coid_stream_flags );

	if( coid_stream_flags == ConnectFlags::fACCESS_MODE_DIRECT ) {	/// direct
		if( !bstream2 ) bstream2 = bstream;
		bstream2->read_ptr(_vtbl);
		bstream2->read_ptr(_me);
		comm_mutex_reg::refmutex* _rmx;
		bstream2->read_ptr(_rmx);
		_mx_reg.init( *_rmx );
		bstream2->acknowledge();
		delete bstream;
	}
	else if( coid_stream_flags == ConnectFlags::fACCESS_MODE_INTERPROCESS ) {	/// interprocess
		if( !bstream2 ) bstream2 = bstream;
		if( coid_interprocess_vtbl ) _vtbl = coid_interprocess_vtbl;
		else {
			MXGUARD( coid_table_mx );
			if( !coid_interprocess_vtbl )
				MudCore_localclient::coid_init_table( coid_stream_flags, coid_interprocess_vtbl );
			_vtbl = coid_interprocess_vtbl;
		}
		_me = (COID_TABLE_MudCore_0_0 *) new MudCore_localclient( bstream, bstream2 );
		bstream2->acknowledge();
		_mx_reg.init( mx_reg, true, "MudCore_client" );
		_mx_reg._p1 = bstream;
	}
	else if( coid_stream_flags == ConnectFlags::fACCESS_MODE_REMOTE ) {	/// remote
		if( !bstream2 ) bstream2 = bstream;
		if( coid_remote_vtbl ) _vtbl = coid_remote_vtbl;
		else {
			MXGUARD( coid_table_mx );
			if( !coid_remote_vtbl )
				MudCore_localclient::coid_init_table( coid_stream_flags, coid_remote_vtbl );
			_vtbl = coid_remote_vtbl;
		}
		_me = (COID_TABLE_MudCore_0_0 *) new MudCore_localclient( bstream, bstream2 );
		bstream2->acknowledge();
		_mx_reg.init( mx_reg, true, "MudCore_client" );
		_mx_reg._p1 = bstream;
	}
	else throw ersFE_EXCEPTION;

	return true;
}

void MudCore_client::setup_stream( binstream& b, uint coid_flags, uint coid_obj_id ) const
{
	if( coid_flags & ConnectFlags::fWITHIN_CHANNEL ) {
		coid_flags &= ~ConnectFlags::fWITHIN_CHANNEL;
		b << StdProtocolMethod::CONNECT;
	}
	uchar conchar = StdProtocolMethod::CONNECT_CHAR;
	b << conchar << version(COID_VERSION);
	b << g_coid_nick << coid_flags << COID_version_MudCore_0_0 << "MudCore";
	b << (uint) 4;
   b.write_ptr(COID_method_flags_MudCore);
   b << coid_obj_id << sysGetPid();
   b.write_ptr(this);
}

opcd MudCore_client::ping_server( const char * coid_address, uint coid_obj_id, uint coid_flags ) const
{
	coid_flags |= ConnectFlags::fPING_SERVER;
	charstr tmp;
	netstream * _coid_bstream = COID_CLIENT::create_stream( "MudCore", coid_address, tmp, (coid_flags & ConnectFlags::fTUNNEL) != 0 );
	if( !_coid_bstream ) return ersFE_UNREACHABLE;
	if( _timeout ) _coid_bstream->set_timeout( _timeout );
	coid_flags &= ~ConnectFlags::fTUNNEL;
	setup_stream( *_coid_bstream, coid_flags, coid_obj_id );
	___COID_CONNECT_TRY
	_coid_bstream->flush();
	opcd _coid_err_code = _coid_bstream->get_error();
	_coid_bstream->acknowledge();
	delete _coid_bstream;
	return _coid_err_code;

	___COID_CONNECT_CATCH
}

opcd MudCore_client::set_timeout( uint milliseconds )
{
	if( milliseconds == _timeout ) return 0;
	_timeout = milliseconds;
	if( !is_connected() ) return 0;
	if( get_connection_type() == ConnectFlags::fACCESS_MODE_DIRECT ) return 0;
	netstream * n = ((COID_CLIENT *) _me)->get_stream();
	if( !n ) return ersFAILED;
	return n->set_timeout( _timeout );
}






opcd MudCore_client::connect_shared( uint coid_obj_id,  const char * coid_address, uint coid_flags  )
{
	_addr.reset();
	extendedGUARD_reg __coid__mxg( _mx_reg, false );
	if( _mx_reg.is_valid() ) {
		if( get_connection_type() == ConnectFlags::fACCESS_MODE_DIRECT ) {	/// direct
			return ersFE_ALREADY_CONNECTED;
		}
		else if( get_connection_type() == ConnectFlags::fACCESS_MODE_INTERPROCESS ) {	/// interprocess
			__coid__mxg.lock();
			if( ((COID_CLIENT *)_me)->is_stream_open() )
				return ersFE_ALREADY_CONNECTED;
		}
		else if( get_connection_type() == ConnectFlags::fACCESS_MODE_REMOTE ) {	/// remote
			__coid__mxg.lock();
			if( ((COID_CLIENT *)_me)->is_stream_open() )
				return ersFE_ALREADY_CONNECTED;
		}
	}

	netstream * _coid_bstream = COID_CLIENT::create_stream( "MudCore", coid_address, _addr, (coid_flags & ConnectFlags::fTUNNEL) != 0 );
	if( !_coid_bstream ) return ersFE_UNREACHABLE;
	if( _timeout ) _coid_bstream->set_timeout( _timeout );
	coid_flags &= ~ConnectFlags::fTUNNEL;
	setup_stream( *_coid_bstream, coid_flags, coid_obj_id );
	___COID_CONNECT_TRY

	_coid_bstream->flush();
	opcd _coid_err_code = _coid_bstream->get_error();
	if( _coid_err_code ) {
		_coid_bstream->acknowledge(true);
		delete _coid_bstream;
		return _coid_err_code;
	}

	uint coid_stream_flags;
	*_coid_bstream >> coid_stream_flags;
	RASSERT( coid_stream_flags & ConnectFlags::xACCESS_MODE );
	setup_members( coid_stream_flags, _coid_bstream, _coid_bstream );

	return _coid_err_code;

	___COID_CONNECT_CATCH
}









opcd MudCore_client::register_client( uint obj )
{
	opcd _coid_err_code;
#ifndef MudCore_NOTHROW
	try {
#endif
		extendedGUARD_lock( _mx_reg );
		if( !_me ) return ersFE_DISCONNECTED;
		_coid_err_code = (_me->*(_vtbl->register_client__0)) (obj);
#ifndef MudCore_NOTHROW
	}
	catch( opcd e ) {_coid_err_code = e;}
#endif
	return _coid_err_code;
}

opcd MudCore_client::unregister_client( uint obj )
{
	opcd _coid_err_code;
#ifndef MudCore_NOTHROW
	try {
#endif
		extendedGUARD_lock( _mx_reg );
		if( !_me ) return ersFE_DISCONNECTED;
		_coid_err_code = (_me->*(_vtbl->unregister_client__1)) (obj);
#ifndef MudCore_NOTHROW
	}
	catch( opcd e ) {_coid_err_code = e;}
#endif
	return _coid_err_code;
}

opcd MudCore_client::get_creature(const charstr & str, void ** cr)
{
	opcd _coid_err_code;
#ifndef MudCore_NOTHROW
	try {
#endif
		extendedGUARD_lock( _mx_reg );
		if( !_me ) return ersFE_DISCONNECTED;
		_coid_err_code = (_me->*(_vtbl->get_creature__2)) (str, cr);
#ifndef MudCore_NOTHROW
	}
	catch( opcd e ) {_coid_err_code = e;}
#endif
	return _coid_err_code;
}

opcd MudCore_client::send_intro_screen(dynarray<charstr> * arr)
{
	opcd _coid_err_code;
#ifndef MudCore_NOTHROW
	try {
#endif
		extendedGUARD_lock( _mx_reg );
		if( !_me ) return ersFE_DISCONNECTED;
		_coid_err_code = (_me->*(_vtbl->send_intro_screen__3)) (arr);
#ifndef MudCore_NOTHROW
	}
	catch( opcd e ) {_coid_err_code = e;}
#endif
	return _coid_err_code;
}



}	/// namespace coid
